package model.code.line;

import lang.asm.ASM;
import lang.asm.DataTypeUtil;
import model.symbol.DataType;
import model.symbol.LocalVariable;
import model.symbol.Register;

/**
 * This is a representation of a parsed memory operand for assembling or error-checking.
 *
 * @author Neil Dickson
 */
public class MemoryOperand {
	/**
	 * This is the final type of the operand, or null if none is able to be determined.
	 */
	public	DataType	type;
	/**
	 * This is the local variable present in the operand, or null if none.
	 */
	public	LocalVariable	localVariable;
	/**
	 * This is an index register that is not multiplied by a factor, or null if none.
	 */
	public	Register	regularIndexReg;
	/**
	 * This is the index register that may be multiplied by a factor, or null if none.
	 */
	public	Register	scaledIndexReg;
	/**
	 * This is the multiplier of the index register that may be multiplied, or 1 if none.
	 */
	public	int			multiplier;
	/**
	 * This is the immediate offset (null if none).
	 */
	public	Immediate	offset;

	public static final byte MATCH_R		= 0;
	public static final byte MATCH_R_2		= 1;
	public static final byte MATCH_R_4		= 2;
	public static final byte MATCH_R_8		= 3;
	public static final byte MATCH_RBP		= 4;
	public static final byte MATCH_RBP_2	= 5;
	public static final byte MATCH_RBP_4	= 6;
	public static final byte MATCH_RBP_8	= 7;
	public static final byte MATCH_RSP		= 8;
	public static final byte MATCH_RIP		= 9;
	public static final byte MATCH_BX		= 10;
	public static final byte MATCH_SI		= 11;
	public static final byte MATCH_DI		= 12;
	public static final byte MATCH_R12		= 13;
	public static final byte MATCH_IMM8S	= DataTypeUtil.TYPE_INDEX_IMM8S;
	public static final byte MATCH_IMM16S	= DataTypeUtil.TYPE_INDEX_IMM16S;
	public static final byte MATCH_IMM32S	= DataTypeUtil.TYPE_INDEX_IMM32S;
	public static final byte MATCH_IMM16U	= DataTypeUtil.TYPE_INDEX_IMM16U;
	public static final byte MATCH_IMM32U	= DataTypeUtil.TYPE_INDEX_IMM32U;
	public static final byte MATCH_IMM16	= DataTypeUtil.TYPE_INDEX_IMM16;
	public static final byte MATCH_IMM32	= DataTypeUtil.TYPE_INDEX_IMM32;


	public MemoryOperand(DataType type, LocalVariable localVariable, Register regularIndexReg, Register scaledIndexReg, int multiplier,Immediate offset) {
		this.type = type;
		this.localVariable = localVariable;
		this.regularIndexReg = regularIndexReg;
		this.scaledIndexReg = scaledIndexReg;
		this.multiplier = multiplier;
		this.offset = offset;
	}

	/**
	 * Replaces the local variable with a register and an offset from that register
	 * @param reg the register to which the local variable is relative (usually bp/ebp/rbp or esp/rsp)
	 * @param offsetFromReg the offset of the variable from the register
	 * @return true if there aren't already two registers here, else false
	 */
	public boolean translateVariable(Register reg,int offsetFromReg) {
		if (scaledIndexReg==null) {
			scaledIndexReg = reg;
			multiplier = 1;
		}
		else if (regularIndexReg==null) {
			regularIndexReg = reg;
		}
		else {
			return false;
		}
		localVariable = null;
		if (offset==null) {
			offset = new Immediate(offsetFromReg);
		}
		else {
			offset.constant += offsetFromReg;
		}
		return true;
	}

	/**
	 * Finds a matching encoding for this memory operand.
	 * NOTE: If swapping regularIndexReg and scaledIndexReg makes the matching consistent, they will be swapped!
	 * @param mode current CPU mode (16, 32, or 64-bit)
	 * @return the matching encoding if there is one, else null
	 */
	public short[] findMatchEncoding(int mode) {
		byte[][] types;
		short[][] encodings;
		// 64-bit mode and 32-bit mode are almost the same, so use the same code and just adjust for differences (like RIP-relative addressing)
		if (mode==64 || mode==32) {
			types = (mode==64) ? ASM.language.addressingTypes64 : ASM.language.addressingTypes32;
			encodings = (mode==64) ? ASM.language.addressingEncodings64 : ASM.language.addressingEncodings32;
			int indexRegType = (mode==64) ? DataTypeUtil.TYPE_INDEX_RG64 : DataTypeUtil.TYPE_INDEX_RG32_32_64;
			for (int i=0;i<types.length;++i) {
				byte[] type = types[i];
				boolean hasImmediate = type[type.length-1]>=MATCH_IMM8S;
				boolean matchesImmediate = offset==null || offset.isZero();
				if (!matchesImmediate && hasImmediate) {
					int immType = DataTypeUtil.getOperandType(offset,mode);
					matchesImmediate = DataTypeUtil.isCompatibleType(immType,type[type.length-1]);
				}
				if (matchesImmediate) {
					int nRegs = hasImmediate?(type.length-1):type.length;
					if (nRegs==2) {
						if (regularIndexReg!=null && scaledIndexReg!=null && regularIndexReg.getType()==indexRegType && scaledIndexReg.getType()==indexRegType) {
							boolean matchesFirst = registerMatches(regularIndexReg.getNumber(),type[0]);
							if (multiplier==1) {
								if (matchesFirst && registerMatches(scaledIndexReg.getNumber(),type[1])) {
									return encodings[i];
								}
								if (registerMatches(regularIndexReg.getNumber(),type[1]) && registerMatches(scaledIndexReg.getNumber(),type[0])) {
									// Swap the registers so that the matching is consistent in order.
									// This only matters for encodings involving RBP or RSP, because other registers can go in either place.
									Register temp = regularIndexReg;
									regularIndexReg = scaledIndexReg;
									scaledIndexReg = temp;
									return encodings[i];
								}
							}
							else if (matchesFirst && scaledRegisterMatches(scaledIndexReg.getNumber(),multiplier,type[1])) {
								return encodings[i];
							}
						}
					}
					else if (nRegs==1) {
						if (regularIndexReg!=null && scaledIndexReg==null) {
							if ((regularIndexReg.getType()==indexRegType && registerMatches(regularIndexReg.getNumber(),type[0])) ||
									(mode==64 && regularIndexReg.getType()==DataTypeUtil.TYPE_INDEX_RIP && type[0]==MATCH_RIP)) {
								return encodings[i];
							}
						}
						else if (regularIndexReg==null && scaledIndexReg!=null) {
							if ((scaledIndexReg.getType()==indexRegType && scaledRegisterMatches(scaledIndexReg.getNumber(),multiplier,type[0])) ||
									(mode==64 && scaledIndexReg.getType()==DataTypeUtil.TYPE_INDEX_RIP && type[0]==MATCH_RIP)) {
								return encodings[i];
							}
						}
					}
					else { // nRegs==0
						if (regularIndexReg==null && scaledIndexReg==null) {
							return encodings[i];
						}
					}
				}
			}
		}
		else { // mode==16
			types = ASM.language.addressingTypes16;
			encodings = ASM.language.addressingEncodings16;
			int indexRegType = DataTypeUtil.TYPE_INDEX_RG16_32_64;
			for (int i=0;i<types.length;++i) {
				byte[] type = types[i];
				boolean hasImmediate = type[type.length-1]>=MATCH_IMM8S;
				boolean matchesImmediate = offset==null || offset.isZero();
				if (!matchesImmediate && hasImmediate) {
					int immType = DataTypeUtil.getOperandType(offset,mode);
					matchesImmediate = DataTypeUtil.isCompatibleType(immType,type[type.length-1]);
				}
				if (matchesImmediate) {
					int nRegs = hasImmediate?(type.length-1):type.length;
					if (nRegs==2) {
						if (regularIndexReg!=null && scaledIndexReg!=null && regularIndexReg.getType()==indexRegType && scaledIndexReg.getType()==indexRegType) {
							if (registerMatches16(regularIndexReg.getNumber(),type[0]) && registerMatches16(scaledIndexReg.getNumber(),type[1])) {
								return encodings[i];
							}
							if (registerMatches16(regularIndexReg.getNumber(),type[1]) && registerMatches16(scaledIndexReg.getNumber(),type[0])) {
								// NOTE: The registers don't need to be swapped because the encoding doesn't depend on the registers.
								return encodings[i];
							}
						}
					}
					else if (nRegs==1) {
						if (regularIndexReg!=null && scaledIndexReg==null && regularIndexReg.getType()==indexRegType) {
							if (registerMatches16(regularIndexReg.getNumber(),type[0])) {
								return encodings[i];
							}
						}
						else if (regularIndexReg==null && scaledIndexReg!=null && scaledIndexReg.getType()==indexRegType) {
							if (registerMatches16(scaledIndexReg.getNumber(),type[0])) {
								return encodings[i];
							}
						}
					}
					else { // nRegs==0
						if (regularIndexReg==null && scaledIndexReg==null) {
							return encodings[i];
						}
					}
				}
			}
		}
		return null;
	}

	private boolean registerMatches(int regNumber,int match) {
		switch (regNumber) {
			case 4: return match==MATCH_RSP;
			case 5:
			case 13:return match==MATCH_RBP;
			case 12: if (match==MATCH_R12) {return true;}
				// fall through to match general case
			default:return match==MATCH_R;
		}
	}
	private boolean scaledRegisterMatches(int regNumber,int multiplier,int match) {
		switch (regNumber) {
			case 4: return (match==MATCH_RSP && multiplier==1);
			case 5:
			case 13:return (match==MATCH_RBP && multiplier==1) || (match==MATCH_RBP_2 && multiplier==2) || (match==MATCH_RBP_4 && multiplier==4) || (match==MATCH_RBP_8 && multiplier==8);
			case 12: if (match==MATCH_R12 && multiplier==1) {return true;}
				// fall through to
			default:return (match==MATCH_R && multiplier==1) || (match==MATCH_R_2 && multiplier==2) || (match==MATCH_R_4 && multiplier==4) || (match==MATCH_R_8 && multiplier==8);
		}
	}
	private boolean registerMatches16(int regNumber,int match) {
		switch (regNumber) {
			case 3: return match==MATCH_BX;
			case 5: return match==MATCH_RBP;
			case 6: return match==MATCH_SI;
			case 7: return match==MATCH_DI;
			default:return false;
		}
	}
}
